#include "ResourceManager.h"
#include "Exception.h"
#include "Archive.h"
#include "ArchiveManager.h"
#include "StringVector.h"
#include "StringConverter.h"
#include "ResourceGroupManager.h"

namespace PVM
{
	ResourceManager::ResourceManager()
	: mNextHandle(1), mMemoryUsage(0), mVerbose(true), mLoadOrder(0)
	{
		mMemoryBudget = std::numeric_limits<unsigned long>::max();
	}
	
	ResourceManager::~ResourceManager()
	{
		destroyAllResourcePools();
		removeAll();
	}
	
	ResourcePtr ResourceManager::create(const std::string& name, const std::string& group,
										bool isManual, ManualResourceLoader* loader, const NameValuePairList* params)
	{
		ResourcePtr ret = ResourcePtr(
		createImpl(name, getNextHandle(), group, isManual, loader, params));
		if (params)
			ret->setParameterList(*params);
		
		addImpl(ret);
		ResourceGroupManager::getSingleton()._notifyResourceCreated(ret);
		return ret;
	}
	
	ResourceManager::ResourceCreateOrRetrieveResult
	ResourceManager::createOrRetrieve(
									  const std::string& name, const std::string& group,
									  bool isManual, ManualResourceLoader* loader,
									  const NameValuePairList* params)
	{
		PVM_LOCK_AUTO_MUTEX
		
		ResourcePtr res = getByName(name, group);
		bool created = false;
		if (res.isNull())
		{
			created = true;
			res = create(name, group, isManual, loader, params);
		}
		
		return ResourceCreateOrRetrieveResult(res, created);
	}
	
	ResourcePtr ResourceManager::prepare(const std::string& name,
										 const std::string& group, bool isManual, ManualResourceLoader* loader,
										 const NameValuePairList* loadParams, bool backgroundThread)
	{
		ResourcePtr r = createOrRetrieve(name, group, isManual, loader, loadParams).first;
		r->prepare(backgroundThread);
		return r;
	}
	
	ResourcePtr ResourceManager::load(const std::string& name,
									  const std::string& group, bool isManual, ManualResourceLoader* loader,
									  const NameValuePairList* loadParams, bool backgroundThread)
	{
		ResourcePtr r = createOrRetrieve(name, group, isManual, loader, loadParams).first;
		r->load(backgroundThread);
		return r;
	}
	
	void ResourceManager::addImpl(ResourcePtr& res)
	{
		PVM_LOCK_AUTO_MUTEX
		
		std::pair<ResourceMap::iterator, bool> result;
		if (ResourceGroupManager::getSingleton().isResourceGroupInGlobalPool(res->getGroup()))
		{
			result = mResources.insert(ResourceMap::value_type(res->getName(), res));
		}
		else
		{
			ResourceWithGroupMap::iterator itGroup = mResourcesWithGroup.find(res->getGroup());
			
			if (itGroup == mResourcesWithGroup.end())
			{
				ResourceMap dummy;
				mResourcesWithGroup.insert(ResourceWithGroupMap::value_type(res->getGroup(), dummy));
				itGroup = mResourcesWithGroup.find(res->getGroup());
			}
			result = itGroup->second.insert(ResourceMap::value_type(res->getName(), res));
		}
		
		if (!result.second)
		{
			if (ResourceGroupManager::getSingleton().getLoadingListener())
			{
				if (ResourceGroupManager::getSingleton().getLoadingListener()->resourceCollision(res.get(), this))
				{
					std::pair<ResourceMap::iterator, bool> insertResult;
					if (ResourceGroupManager::getSingleton().isResourceGroupInGlobalPool(res->getGroup()))
					{
						insertResult = mResources.insert(ResourceMap::value_type(res->getName(), res));
					}
					else
					{
						ResourceWithGroupMap::iterator itGroup = mResourcesWithGroup.find(res->getGroup());
						insertResult = itGroup->second.insert(ResourceMap::value_type(res->getName(), res));
					}
					if (!insertResult.second)
					{
						PVM_EXCEPT("Resource with the name " + res.getName() + 
								   " already exists.", "ResourceManager::add");
					}
					
					std::pair<ResourceHandleMap::iterator, bool> resultHandle = 
					mResourcesByHandle.insert(ResourceHandleMap::value_type(res->getHandle(), res));
					if (!resultHandle.second)
					{
						PVM_EXCEPT("Resource with the handle " + 
								   StringConverter::toString((long)(res->getHandle())) + 
								   " already exists.", "ResourceManager::add");
					}
				}
			}
		}
		else
		{
			std::pair<ResourceHandleMap::iterator, bool> resultHandle = 
			mResourcesByHandle.insert(ResourceHandleMap::value_type(res->getHandle(), res));
			if (!resultHandle.second)
			{
				PVM_EXCEPT("Resource with the handle " +
						   StringConverter::toString((long)(res->getHandle())) +
						   " already exists.", "ResourceManager::add");
			}
		}
	}
	
	void ResourceManager::removeImpl(ResourcePtr& res)
	{
		PVM_LOCK_AUTO_MUTEX
		
		if (ResourceGroupManager::getSingleton().isResourceGroupInGlobalPool(res->getGroup()))
		{
			ResourceMap::iterator nameIt = mResources.find(res->getName());
			if (nameIt != mResources.end())
			{
				mResources.erase(nameIt);
			}
		}
		else
		{
			ResourceWithGroupMap::iterator groupIt = mResourcesWithGroup.find(res->getGroup());
			if (groupIt != mResourcesWithGroup.end())
			{
				ResourceMap::iterator nameIt = groupIt->second.find(res->getName());
				if (nameIt != groupIt->second.end())
				{
					groupIt->second.erase(nameIt);
				}
				
				if (groupIt->second.empty())
				{
					mResourcesWithGroup.erase(groupIt);
				}
			}
		}
		
		ResourceHandleMap::iterator handleIt = mResourcesByHandle.find(res->getHandle());
		if (handleIt != mResourcesByHandle.end())
		{
			mResourcesByHandle.erase(handleIt);
		}
		
		ResourceGroupManager::getSingleton()._notifyResourceRemoved(res);
	}
	
	void ResourceManager::setMemoryBudget(size_t bytes)
	{
		mMemoryBudget = bytes;
		checkUsage();
	}
	
	size_t ResourceManager::getMemoryBudget(void) const
	{
		return mMemoryBudget;
	}
	
	void ResourceManager::unload(const std::string& name)
	{
		ResourcePtr res = getByName(name);
		
		if (!res.isNull())
		{
			res->unload();
		}
	}
	
	void ResourceManager::unload(ResourceHandle handle)
	{
		ResourcePtr res = getByHandle(handle);
		
		if (!res.isNull())
		{
			res->unload();
		}
	}
	
	void ResourceManager::unloadAll(bool reloadableOnly)
	{
		PVM_LOCK_AUTO_MUTEX
		
		ResourceMap::iterator i, iend;
		iend = mResources.end();
		for (i = mResources.begin(); i != iend; ++i)
		{
			if (!reloadableOnly || i->second->isReloadable())
			{
				i->second->unload();
			}
		}
	}
	
	void ResourceManager::reloadAll(bool reloadableOnly)
	{
		PVM_LOCK_AUTO_MUTEX
		
		ResourceMap::iterator i, iend;
		iend = mResources.end();
		for (i = mResources.begin(); i != iend; ++i)
		{
			if (!reloadableOnly || i->second->isReloadable())
			{
				i->second->reload();
			}
		}
	}
	
	void ResourceManager::unloadUnreferencedResources(bool reloadableOnly)
	{
		PVM_LOCK_AUTO_MUTEX
		
		ResourceMap::iterator i, iend;
		iend = mResources.end();
		for (i = mResources.begin(); i != iend; ++i)
		{
			if (i->second.useCount() == ResourceGroupManager::RESOURCE_SYSTEM_NUM_REFERENCE_COUNTS)
			{
				Resource* res = i->second.get();
				if (!reloadableOnly || res->isReloadable())
				{
					res->unload();
				}
			}
		}
	}
	
	void ResourceManager::reloadUnreferencedResources(bool reloadableOnly)
	{
		PVM_LOCK_AUTO_MUTEX
		
		ResourceManager::iterator i, iend;
		iend = mResources.end();
		for (i = mResources.begin(); i != iend; ++i)
		{
			if (i->second.useCount() == ResourceGroupManager::RESOURCE_SYSTEM_NUM_REFERENCE_COUNTS)
			{
				Resource* res = i->second.get();
				if (!reloadableOnly || res->isReloadable())
				{
					res->reload();
				}
			}
		}
	}
	
	void ResourceManager::remove(ResourcePtr& res)
	{
		removeImpl(res);
	}
	
	void ResourceManager::remove(const std::string& name)
	{
		ResourcePtr res = getByName(name);
		
		if (!res.isNull())
		{
			removeImpl(res);
		}
	}
	
	void ResourceManager::remove(ResourceHandle handle)
	{
		ResourcePtr res = getByHandle(handle);
		
		if (!res.isNull())
		{
			removeImpl(res);
		}
	}
	
	void ResourceManager::removeAll(void)
	{
		PVM_LOCK_AUTO_MUTEX
		
		mResources.clear();
		mResourcesWithGroup.clear();
		mResourcesByHandle.clear();
		ResourceGroupManager::getSingleton()._notifyAllResourcesRemoved(this);
	}
	
	void ResourceManager::removeUnreferencedResources(bool reloadableOnly)
	{
		PVM_LOCK_AUTO_MUTEX
		
		ResourceMap::iterator i, iend;
		iend = mResources.end();
		for (i = mResources.begin(); i != iend; )
		{
			if (i->second.useCount() == ResourceGroupManager::RESOURCE_SYSTEM_NUM_REFERENCE_COUNTS)
			{
				Resource* res = (i++)->second.get();
				if (!reloadableOnly || res->isReloadable())
				{
					remove(res->getHandle());
				}
			}
			else
			{
				++i;
			}
		}
	}
	
	ResourcePtr ResourceManager::getByName(const std::string& name, const std::string& groupName)
	{
		ResourcePtr res;
		
		if (!ResourceGroupManager::getSingleton().isResourceGroupInGlobalPool(groupName))
		{
			PVM_LOCK_AUTO_MUTEX
			ResourceWithGroupMap::iterator itGroup = mResourcesWithGroup.find(groupName);
			
			if (itGroup != mResourcesWithGroup.end())
			{
				ResourceMap::iterator it = itGroup->second.find(name);
				
				if (it != itGroup->second.end())
				{
					res = it->second;
				}
			}
		}
		
		if (res.isNull())
		{
			PVM_LOCK_AUTO_MUTEX
			ResourceMap::iterator it = mResources.find(name);
			
			if (it != mResources.end())
			{
				res = it->second;
			}
			else
			{
				if (groupName == ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME)
				{
					ResourceWithGroupMap::iterator iter = mResourcesWithGroup.begin();
					ResourceWithGroupMap::iterator iterE = mResourcesWithGroup.end();
					for (; iter != iterE; iter++)
					{
						ResourceMap::iterator resMapIt = iter->second.find(name);
						
						if (resMapIt != iter->second.end())
						{
							res = resMapIt->second;
						}
					}
				}
			}
		}
		
		return res;
	}
	
	ResourcePtr ResourceManager::getByHandle(ResourceHandle handle)
	{
		PVM_LOCK_AUTO_MUTEX
		
		ResourceHandleMap::iterator it = mResourcesByHandle.find(handle);
		if (it == mResourcesByHandle.end())
		{
			return ResourcePtr();
		}
		else
		{
			return it->second;
		}
	}
	
	ResourceHandle ResourceManager::getNextHandle(void)
	{
		PVM_LOCK_AUTO_MUTEX
		
		return mNextHandle++;
	}
	
	void ResourceManager::checkUsage(void)
	{
		
	}
	
	void ResourceManager::_notifyResourceTouched(Resource* res)
	{
	}
	
	void ResourceManager::_notifyResourceLoaded(Resource* res)
	{
		mMemoryUsage += res->getSize();
	}
	
	void ResourceManager::_notifyResourceUnloaded(Resource* res)
	{
		mMemoryUsage += -(res->getSize());
	}
	
	ResourceManager::ResourcePool* ResourceManager::getResourcePool(const std::string& name)
	{
		PVM_LOCK_AUTO_MUTEX
		
		ResourcePoolMap::iterator i = mResourcePoolMap.find(name);
		if (i == mResourcePoolMap.end())
		{
			i = mResourcePoolMap.insert(ResourcePoolMap::value_type(name,
						new ResourcePool(name))).first;
		}
		return i->second;
	}
	
	void ResourceManager::destroyResourcePool(ResourcePool* pool)
	{
		PVM_LOCK_AUTO_MUTEX
		
		ResourcePoolMap::iterator i = mResourcePoolMap.find(pool->getName());
		if (i != mResourcePoolMap.end())
			mResourcePoolMap.erase(i);
		
		delete pool;
	}
	
	void ResourceManager::destroyResourcePool(const std::string& name)
	{
		PVM_LOCK_AUTO_MUTEX
		
		ResourcePoolMap::iterator i = mResourcePoolMap.find(name);
		if (i != mResourcePoolMap.end())
		{
			delete i->second;
			mResourcePoolMap.erase(i);
		}
	}
	
	void ResourceManager::destroyAllResourcePools()
	{
		PVM_LOCK_AUTO_MUTEX
		
		for (ResourcePoolMap::iterator i = mResourcePoolMap.begin();
			 i != mResourcePoolMap.end(); ++i)
			delete i->second;
		mResourcePoolMap.clear();
	}
	
	ResourceManager::ResourcePool::ResourcePool(const std::string& name)
	: mName(name)
	{
	}
	
	ResourceManager::ResourcePool::~ResourcePool()
	{
		clear();
	}
	
	const std::string& ResourceManager::ResourcePool::getName() const
	{
		return mName;
	}
	
	void ResourceManager::ResourcePool::clear()
	{
		PVM_LOCK_AUTO_MUTEX
		for (ItemList::iterator i = mItems.begin(); i != mItems.end(); ++i)
		{
			(*i)->getCreator()->remove((*i)->getHandle());
		}
		
		mItems.clear();
	}
}
